import {IFsm} from "./IFsm";
import { IReference } from "../Base/ReferencePool/IReference";
import {FsmState} from "./FsmState";
import {ReferencePool} from "../Base/ReferencePool/ReferencePool";
import ccclass = cc._decorator.ccclass;
import FsmBase from "./FsmBase";
@ccclass("Fsm")
export class Fsm<T> implements FsmBase,IReference,IFsm<T>{

    public static Create<T>(name:string,owner:T,states:FsmState<T>[]):Fsm<T>{
        if (owner == null)
        {
            throw new Error("FSM owner is invalid.");
        }

        if (states == null || states.length < 1)
        {
            throw new Error("FSM states is invalid.");
        }
        let fsm:Fsm<any> = ReferencePool.Acquire(Fsm);
        fsm.Name = name;
        fsm._owner = owner;
        fsm._isDestroyed = false;
        for (let i = 0; i < states.length; i++) {
            let state = states[i];
            if (state == null)
            {
                throw new Error("FSM states is invalid.");
            }
            let stateType:string = state.name;
            if (fsm._states.has(stateType))
            {
                throw new Error("FSM '{0}' state '{1}' is already exist.");
            }
            fsm._states.set(stateType, state);
            state.OnInit(fsm);
        }
        return fsm;
    }


    private _owner:T;
    private _name:string;
    private readonly _states:Map<string,FsmState<T>>;
    private readonly _datas:Map<string,{}>;
    private _currentState:FsmState<T>;
    private _currentStateTime:number;
    private _isDestroyed:boolean;

    constructor() {
        this._owner = null;
        this._states = new Map<string, FsmState<T>>();
        this._datas = new Map<string, {}>();
        this._currentState = null;
        this._isDestroyed = true;
        this._currentStateTime = 0;
        this._name = "";
    }

    private isSub(classtype):classtype is FsmState<T>{
        return classtype instanceof FsmState;
    }

    OwnerType() {
        return Fsm.prototype;
    }

    CurrentStateName(): string {
       return this._currentState != null?this._currentState.name:null;
    }
    Update(elapseSeconds: number, realElapseSeconds: number): void {
        if (this._currentState == null)
        {
            return;
        }
        this._currentStateTime += elapseSeconds;
        this._currentState.OnUpdate(this, elapseSeconds, realElapseSeconds);
    }
    Shutdown(): void {
        ReferencePool.Release(this);
    }

    Clear(): void {
        if (this._currentState != null)
        {
            this._currentState.OnLeave(this, true);
        }

        this._states.forEach(state=>{
            state.OnDestroy(this);
        });


        this.Name = null;
        this._owner = null;
        this._states.clear();
        this._datas.clear();
        this._currentState = null;
        this._currentStateTime = 0;
        this._isDestroyed = true;
    }

    CurrentState(): FsmState<T> {
        return this._currentState;
    }

    CurrentStateTime(): number {
        return this._currentStateTime;
    }

    FsmStateCount(): number {
        return this._states.size;
    }

    GetAllStates(): FsmState<T>[] {
        let result = [];
        this._states.forEach(value => {
            result.push(value);
        });
        return result;
    }

    GetData(name: string): {} {
        if (name == undefined || name == "")
        {
            throw new Error("Data name is invalid.");
        }
        return this._datas.get(name);
    }

    GetState(stateType:{prototype:FsmState<T>}): FsmState<T> {
        if(!this.isSub(stateType)){
            throw new Error("State type is invalid.");
        }
        return this._states.get(stateType.constructor.name);
    }

    HasData(name: string): boolean {
        if(name == "" || name == undefined){
            throw new Error("Data name is invalid");
        }
        return this._datas.has(name);
    }

    HasState(stateType:{prototype:FsmState<T>}): boolean {
        return this._states.has(stateType.prototype.constructor.name);
    }


    IsRunning(): boolean {
        return this._currentState != null;
    }

    get Name(): string {
        return this._name;
    }

    set Name(value:string){
        this._name = value??"";
    }


    Owner(): T {
        return this._owner;
    }

    RemoveData(name: string): boolean {
        if(name == "" || name == undefined){
            throw new Error("Data name is invalid.");
        }
        return this._datas.delete(name);
    }

    SetData(name: string, data: {}): void {
        if(name == "" || name == undefined){
            throw new Error("Data name is invalid.");
        }

        this._datas.set(name,data);
    }

    Start(stateType:{prototype:FsmState<T>}): void {
        if(this.IsRunning()){
            throw new Error("FSM is running, can not start again.");
        }

        let state:FsmState<T> = this.GetState(stateType);
        if(state == null){
            throw new Error("FSM {state} can not start state '{1}' which is not exist.");
        }
        this._currentStateTime = 0;
        this._currentState = state;
        this._currentState.OnEnter(this);

    }

    IsDestroy(): boolean {
        return this._isDestroyed;
    }

    ChangeState(stateType:{prototype:FsmState<T>}):void{
        if (this._currentState == null)
        {
            throw new Error("Current state is invalid.");
        }
        let state:FsmState<T> = this.GetState(stateType);
        if (state == null)
        {
            throw new Error("FSM "+state.name+" can not change state to '{1}' which is not exist.");
        }
        this._currentState.OnLeave(this, false);
        this._currentStateTime = 0;
        this._currentState = state;
        this._currentState.OnEnter(this);
    }



}